/*    
 * Copyright (C) 2006  ankostis, Stefan Kuper

 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.googlecode.weblogic_jaxb1codec;

import java.io.IOException;
import java.io.Reader;

import weblogic.xml.schema.binding.DeserializationContext;
import weblogic.xml.stream.Attribute;
import weblogic.xml.stream.AttributeIterator;
import weblogic.xml.stream.CharacterData;
import weblogic.xml.stream.EndPrefixMapping;
import weblogic.xml.stream.Space;
import weblogic.xml.stream.StartPrefixMapping;
import weblogic.xml.stream.XMLEvent;
import weblogic.xml.stream.XMLInputStream;
import weblogic.xml.stream.XMLStreamException;
import weblogic.xml.stream.events.StartElementEvent;

/**
 * @author SK, ankostis
 * 
 * Note: The XMLInputStream returns null for the default preffix.
 */
class DeserializerReader extends Reader {

//    private DeserializationContext context;
    
    private int nodeDepth;

	private XMLInputStream inputStream;

	private StringBuffer sBuffer = new StringBuffer();

//    private byte[] byteBuffer = null;

    private int marker;

    /**
     * Usefull for closing empty elements directly,
     * that is, to write '<someElement/>' instead of '<someElement></someElement>.
     */
    private boolean elementStarted = false;

	private boolean finishedReading = false;

    private NamespaceStack xmlnsStack;

    private boolean debug;


    /**
     * @param inputStream The {@link XMLInputStream}.
     * @param debug
     * @throws XMLStreamException
     */
    public DeserializerReader(XMLInputStream inputStream, DeserializationContext context, boolean debug) {
        this.inputStream = inputStream;
//        this.context = context;
        this.debug = debug;
        this.xmlnsStack = new NamespaceStack(debug);
    }

    public void close() throws IOException {
        this.inputStream.close();
    }

    public int read(char[] outbuf, int destOffset, int maxChars) throws IOException {
        int charsLeft = sBuffer.length() - marker;

        if (finishedReading && charsLeft == 0) {
            return -1;
        }
    
        // Keep building xml string until size is at least maxChars.
        while (!finishedReading && charsLeft < maxChars) {
            buildXMLEventString();
            charsLeft = sBuffer.length() - marker;
//            System.out.print(".");//Usefull to see outbuf allocation.
        }
        
        // The next are indexes into the buffer.
        int charsToCopy = Math.min(charsLeft, maxChars);
        int lastCopiedChar = marker + charsToCopy; // This would become the new marker.
        
        sBuffer.getChars(marker, lastCopiedChar, outbuf, destOffset);
        if (debug)
            System.out.println(sBuffer.toString().substring(marker, lastCopiedChar));
        
        if (charsLeft == charsToCopy) { // By construction, charsToCopy <= charsLeft. 
            sBuffer = new StringBuffer();
            marker = 0;
        } else
            marker = lastCopiedChar;
        
        return charsToCopy;
    }

	/**
	 * @throws XMLStreamException
	 * 
	 */
	private void buildXMLEventString() throws XMLStreamException {
		
        if (!inputStream.hasNext()) {
            finishedReading = true;
            return;
        }
            
		XMLEvent ev = inputStream.next();

        // The next piece of code is for
        // closing empty elements directly,
        // ie to write '<someElement/>' instead of '<someElement></someElement>
        if (ev.isEndElement()) {
            if (elementStarted)
                sBuffer.append("/>");
            else
                sBuffer.append("</" + ev.getName().getQualifiedName() + ">");

            // Update node depth.
            nodeDepth--;
            
            // Skip reading elements after our top-most element.
            if (nodeDepth == 0) {
                finishedReading = true;
                return;
            } else if (nodeDepth <= 0) {
                throw new XMLStreamException("Unexpected situation while deserializing input, read to many nodes! Current depth: " + nodeDepth);
            }
            
        } else if (elementStarted) {
            sBuffer.append(">");
        }

        elementStarted = false;

        
        switch (ev.getType()) {
        case XMLEvent.START_ELEMENT:
            elementStarted = true;

            StartElementEvent el = (StartElementEvent) ev;

            sBuffer.append("<" + el.getName().getQualifiedName());

            xmlnsStack.addNewNamespaceDeclAttrs(sBuffer);
            
            AttributeIterator it = el.getAttributes();
            while (it.hasNext()) {
                Attribute at = it.next();

                // HACK: The attr.getQualifiedName() does NOT add prefixes for atributes that 
                // are at the same namespace as the containing element!!!
                String attrName = null;
                String prefix = at.getName().getPrefix();
                
                if (prefix == null || prefix.length() == 0) {
                    prefix = el.getName().getPrefix();
                    if (prefix != null && prefix.length() != 0)
                        attrName = prefix + ":" + at.getName().getLocalName();
                }
                
                if (attrName == null)
                        attrName = at.getName().getQualifiedName();
                
                sBuffer.append(" " + attrName + "=\"" + at.getValue() + "\"");
            }

            // Update node depth.else

            nodeDepth++;

            break;
        case XMLEvent.CHARACTER_DATA:
            CharacterData cdata = (CharacterData) ev;
            if (cdata.hasContent())
                sBuffer.append(cdata.getContent());
            
            break;
        case XMLEvent.SPACE:
            Space spc = (Space) ev;
            if (spc.hasContent())
                sBuffer.append(spc.getContent());
            
            break;
        case XMLEvent.START_PREFIX_MAPPING:
            StartPrefixMapping spm = (StartPrefixMapping) ev;
            this.xmlnsStack.pushMapping(spm.getPrefix(), spm.getNamespaceUri());
            
            break;
        case XMLEvent.END_PREFIX_MAPPING:
            EndPrefixMapping epm = (EndPrefixMapping) ev;
            this.xmlnsStack.popMapping(epm.getPrefix());
            
            break;
        case XMLEvent.END_DOCUMENT:
        case XMLEvent.END_ELEMENT:
        case XMLEvent.CHANGE_PREFIX_MAPPING:
        case XMLEvent.START_DOCUMENT:
//                System.out.println("\nDeserl event["+ev.getTypeAsString() +"]: " + ev);
            
            break;
        default:
            System.out.println("\nUNKNOWN Deserl event["+ev.getTypeAsString() +"]: " + ev);
            break;
        }
	}

}
